package helper.base

import helper.base.CloneHelper.Cloneable
import helper.base.CloneHelper.Expand.StringList.cloneList
import helper.base.ConstraintHelper.CompareOp.Companion.geOp
import helper.base.ConstraintHelper.CompareOp.Companion.leOp
import helper.base.ConstraintHelper.CompareOp.Expand.toCompareOp
import helper.base.ConstraintHelper.CompositeConstraint.Expand.toCompositeConstraint
import helper.base.ConstraintHelper.DiffHelper.Expand.toDiffDouble
import helper.base.ConstraintHelper.DiffHelper.Expand.toDiffString
import helper.base.ConstraintHelper.JoinOp.Companion.andOp
import helper.base.ConstraintHelper.N1Constraint.Companion.GetWConstraint.getV1N1Constraint
import helper.base.ConstraintHelper.N1Constraint.Companion.GetWConstraint.getV2N1Constraint
import helper.base.ConstraintHelper.W1N2Constraint.Companion.GetWConstraint.getV1N2Constraint
import helper.base.ConstraintHelper.W1N2Constraint.Companion.GetWConstraint.getV2N2Constraint
import helper.base.ToTypeHelper.ToTypeTuple
import helper.base.TupleHelper.A2TupleExpand.getIntRangeLength
import helper.base.math.IntRangeHelper

object ConstraintHelper {
    object DiffHelper {
        object Expand {
            fun ArrayList<String>.toDiffString(): String {
                return this.joinToString(separator = "-")
            }

            fun ArrayList<String>.toDoubleList(
                vars: A2LHM<String, Double>,
            ): ArrayList<Double> {
                val convertedList = ArrayList<Double>()
                for (item in this) {
                    require(vars.containsKey(item))
                    convertedList.add(vars[item]!!)
                }

                return convertedList
            }

            fun ArrayList<Double>.toDiffDouble(): Double {
                require(size > 0)
                var totalD = 0.0
                for ((i, d) in this.withIndex()) {
                    if (i == 0) {
                        totalD += d
                    } else {
                        totalD -= d
                    }
                }
                return totalD
            }

            fun ArrayList<String>.toDiffDouble(
                vars: LinkedHashMap<String, Double>,
            ): Double {
                return this.toDoubleList(vars).toDiffDouble()
            }
        }
    }

    class CompareOp(
        val name: String,
        val meetFun: (Double, Double) -> Boolean,
    ) : Cloneable {
        companion object {
            val eOp = CompareOp("=") { v1, v2 -> v1 == v2 }
            val lOp = CompareOp("<") { v1, v2 -> v1 < v2 }
            val gOp = CompareOp(">") { v1, v2 -> v1 > v2 }
            val leOp = CompareOp("<=") { v1, v2 -> v1 <= v2 }
            val geOp = CompareOp(">=") { v1, v2 -> v1 >= v2 }

            val compareOpList = arrayOf(
                eOp,
                lOp,
                gOp,
                leOp,
                geOp,
            )

            val stringCompareOpLHM = A2LHM<String, CompareOp>().also { lhm ->
                compareOpList.forEach {
                    lhm[it.name] = it
                }
            }

            val oppositeCompareOperatorLHM = A2LHM<CompareOp, CompareOp>().also {
                it[eOp] = eOp
                it[lOp] = gOp
                it[gOp] = lOp
                it[leOp] = geOp
                it[geOp] = leOp
            }
        }

        object Expand {
            fun String.toCompareOp(): CompareOp {
                return stringCompareOpLHM[this]!!
            }
        }

        val oppositeCompareOp: CompareOp
            get() {
                return oppositeCompareOperatorLHM[this]!!
            }

        val oppositeCompareOpName: String
            get() {
                return oppositeCompareOp.name
            }

        override fun clone(): CompareOp {
            return CompareOp(name, meetFun)
        }

        override fun toString(): String {
            return name
        }
    }

    class JoinOp(
        val name: String,
        val meetFun: (
            CompositeConstraint,
            CompositeConstraint,
            LinkedHashMap<String, Double>,
        ) -> Boolean,
    ) {
        companion object {
            val andOp = JoinOp("and") { c1, c2, vars ->
                c1.meet(vars) && c2.meet(vars)
            }
            val orOp = JoinOp("or") { c1, c2, vars ->
                c1.meet(vars) || c2.meet(vars)
            }
        }
    }

    //0or1
    interface N0N1Constraint {
        fun meet(
            vars: A2LHM<String, Double>?
        ): Boolean
    }

    //1or2
    //其中var相同
    interface W1N1N2Constraint : Cloneable {
        fun getDiffString(): String
        override fun clone(): W1N1N2Constraint
    }

    class N0Constraint(
        val boolean: Boolean,
    ) : N0N1Constraint {
        override fun meet(
            vars: A2LHM<String, Double>?
        ): Boolean {
            return boolean
        }
    }

    open class N1Constraint(
        val vs: ArrayList<String>,
        val cp: CompareOp,
        val d: Double,
    ) : N0N1Constraint, W1N1N2Constraint {
        companion object {
            object GetWConstraint {
                fun getV1N1Constraint(
                    v1: String,
                    c: CompareOp,
                    d: Double,
                ): N1Constraint {
                    return N1Constraint(
                        arrayListOf(v1),
                        c,
                        d,
                    )
                }

                fun getV2N1Constraint(
                    v1: String,
                    v2: String,
                    c: CompareOp,
                    d: Double,
                ): N1Constraint {
                    return N1Constraint(
                        arrayListOf(v1, v2),
                        c,
                        d,
                    )
                }
            }
        }

        override fun clone(): N1Constraint {
            return N1Constraint(
                vs.cloneList(),
                cp.clone(),
                d,
            )
        }

        override fun meet(
            vars: A2LHM<String, Double>?
        ): Boolean {
            require(vars != null)
            return cp.meetFun(
                vs.toDiffDouble(vars),
                d,
            )
        }

        override fun getDiffString(): String {
            return vs.toDiffString()
        }

        override fun toString(): String {
            val sb = StringBuilder()
            sb.append(vs.toDiffString())
            sb.append(this.cp)
            sb.append(this.d)
            return sb.toString()
        }
    }

    //单值
    class N1EqConstraint(
        vs: ArrayList<String>,
        d: Double,
    ) : N1Constraint(
        vs,
        CompareOp.eOp,
        d,
    )

    //0,1,2,...
    open class CompositeConstraint(
        val cc0: N0N1Constraint? = null,
        val cc1: CompositeConstraint? = null,
        val op: JoinOp? = null,
        val cc2: CompositeConstraint? = null,
    ) {
        companion object {
            fun getCompositeConstraint(
                cc1: CompositeConstraint? = null,
                op: JoinOp? = null,
                cc2: CompositeConstraint? = null,
            ): CompositeConstraint {
                return CompositeConstraint(
                    null,
                    cc1,
                    op,
                    cc2,
                )
            }

            val trueConstraint = N0Constraint(true).toCompositeConstraint()
            val falseConstraint = N0Constraint(false).toCompositeConstraint()
        }

        object Expand {
            fun N0Constraint.toCompositeConstraint(): CompositeConstraint {
                return CompositeConstraint(this)
            }

            fun N1Constraint.toCompositeConstraint(): CompositeConstraint {
                return CompositeConstraint(this)
            }

            private val aStringToW1N2ConstraintTuple = object : ToTypeTuple<String, W1N2Constraint>() {
                //目前只支持"0<=x<90"中的"x"这种单变量
                //不支持"x-y"这样的变量
                override fun privateToF(e: String): W1N2Constraint {
                    val regex = Regex(
                        "(-?\\d+(?:\\.\\d+)?)(<=|<)(\\w+)(<=|<)(-?\\d+(?:\\.\\d+)?)"
                    )
                    val matchResult = regex.find(e)
                    require(matchResult != null)
                    val (num1, symbol1, variable, symbol2, num2) = matchResult.destructured
                    return getV1N2Constraint(
                        variable,
                        symbol1.toCompareOp().oppositeCompareOp,
                        num1.toDouble(),
                        symbol2.toCompareOp(),
                        num2.toDouble(),
                    )
                }
            }

            fun String.toW1N2Constraint(): W1N2Constraint {
                return aStringToW1N2ConstraintTuple.toF(this)
            }
        }

        fun meet(
            vars: A2LHM<String, Double>?,
        ): Boolean {
            return if (cc0 != null) {
                cc0.meet(vars)
            } else {
                require(cc1 != null)
                if (op == null) {
                    cc1.meet(vars)
                } else {
                    require(cc2 != null)
                    op.meetFun(cc1, cc2, vars!!)
                }
            }
        }

        override fun toString(): String {
            val sb = StringBuilder()
            if (cc0 != null) {
                sb.append(cc0.toString())
            } else {
                if (cc1 != null) {
                    sb.append("(${cc1})")
                    require(op != null)
                    sb.append(" ${op.name} ")
                }
                sb.append(cc2.toString())
            }
            return sb.toString()
        }
    }

    class W1N2Constraint(
        val vs: ArrayList<String>,
        val c1: CompareOp,
        val d1: Double,
        val c2: CompareOp,
        val d2: Double,
    ) : CompositeConstraint(
        null,
        N1Constraint(vs, c1, d1).toCompositeConstraint(),
        andOp,
        N1Constraint(vs, c2, d2).toCompositeConstraint(),
    ), W1N1N2Constraint {
        companion object {
            object GetWConstraint {
                fun getV1N2Constraint(
                    v1: String,
                    c1: CompareOp,
                    d1: Double,
                    c2: CompareOp,
                    d2: Double,
                ): W1N2Constraint {
                    return W1N2Constraint(
                        arrayListOf(v1),
                        c1,
                        d1,
                        c2,
                        d2,
                    )
                }

                fun getV2N2Constraint(
                    v1: String,
                    v2: String,
                    c1: CompareOp,
                    d1: Double,
                    c2: CompareOp,
                    d2: Double,
                ): W1N2Constraint {
                    return W1N2Constraint(
                        arrayListOf(v1, v2),
                        c1,
                        d1,
                        c2,
                        d2,
                    )
                }
            }
        }

        override fun clone(): W1N2Constraint {
            return W1N2Constraint(
                vs.cloneList(),
                c1.clone(),
                d1,
                c2.clone(),
                d2,
            )
        }

        override fun getDiffString(): String {
            return vs.toDiffString()
        }

        override fun toString(): String {
            val sb = StringBuilder()
            sb.append(d1)
            sb.append(c1.oppositeCompareOpName)
            sb.append(vs.toDiffString())
            sb.append(c2)
            sb.append(d2)
            return sb.toString()
        }

        fun getMinMaxIntPair(): A2Tuple<Int, Int> {
            return IntRangeHelper.getMinMaxIntPair(
                c1 == geOp,
                d1,
                d2,
                c2 == leOp,
            )
        }

        fun getIntRangeLength(): Int {
            return this.getMinMaxIntPair().getIntRangeLength()
        }
    }

    object W1N1N2ConstraintHelper {
        fun getV1Constraint(
            v1: String,
            c: CompareOp,
            d: Double,
        ): N1Constraint {
            return getV1N1Constraint(v1, c, d)
        }

        fun getV1Constraint(
            v1: String,
            c1: CompareOp,
            d1: Number,
            c2: CompareOp,
            d2: Number,
        ): W1N2Constraint {
            return getV1N2Constraint(
                v1,
                c1,
                d1.toDouble(),
                c2,
                d2.toDouble(),
            )
        }

        fun getV2Constraint(
            v1: String,
            v2: String,
            c: CompareOp,
            d: Double,
        ): N1Constraint {
            return getV2N1Constraint(v1, v2, c, d)
        }

        fun getV2Constraint(
            v1: String,
            v2: String,
            c1: CompareOp,
            d1: Double,
            c2: CompareOp,
            d2: Double,
        ): W1N2Constraint {
            return getV2N2Constraint(v1, v2, c1, d1, c2, d2)
        }

        fun getXConstraint(
            c: CompareOp,
            d: Double,
        ): N1Constraint {
            return getV1Constraint("x", c, d)
        }

        fun getXConstraint(
            c1: CompareOp,
            d1: Double,
            c2: CompareOp,
            d2: Double,
        ): W1N2Constraint {
            return getV1Constraint("x", c1, d1, c2, d2)
        }

        fun getYConstraint(
            c: CompareOp,
            d: Double,
        ): N1Constraint {
            return getV1Constraint("y", c, d)
        }

        fun getYConstraint(
            c1: CompareOp,
            d1: Double,
            c2: CompareOp,
            d2: Double,
        ): W1N2Constraint {
            return getV1Constraint("y", c1, d1, c2, d2)
        }

        fun getXYConstraint(
            c: CompareOp,
            d: Double,
        ): N1Constraint {
            return getV2Constraint("x", "y", c, d)
        }

        fun getXYConstraint(
            c1: CompareOp,
            d1: Double,
            c2: CompareOp,
            d2: Double,
        ): W1N2Constraint {
            return getV2Constraint("x", "y", c1, d1, c2, d2)
        }
    }
}